import { cloneDeep } from 'lodash-es';
import { stringify } from 'qs';

import { ContentTypeEnum, RequestEnum } from '@/enums/httpEnum';
import { isFunction } from '@/utils/is';
import type {
  AjaxResult,
  RequestOptions,
  UniRequestFailResult,
  UniRequestSuccessResult,
} from '#/request';

import { getStatusLable } from '../checkStatus';
import type { CreateUniRequestOptions, UniRequestOptions } from './uniTransform';

/** uni request 组件 */
export class UniRequest {
  private uniTask: UniApp.RequestTask | UniApp.UploadTask | UniApp.DownloadTask;
  private readonly options: CreateUniRequestOptions;

  constructor(options: CreateUniRequestOptions) {
    this.options = { ...options };
  }

  private getTransform() {
    const { transform } = this.options;
    return transform;
  }

  /** 获取任务 */
  getTask<
    T extends UniApp.RequestTask | UniApp.UploadTask | UniApp.DownloadTask = UniApp.RequestTask,
  >() {
    return this.uniTask as T;
  }

  /**
   * 中断请求
   * @tutorial https://uniapp.dcloud.net.cn/api/request/request.html#request
   * @uniPlatform {
   * "app": {
   * "android": {
   * "osVer": "4.4",
   * "uniVer": "√",
   * "unixVer": "3.9.0"
   * },
   * "ios": {
   * "osVer": "9.0",
   * "uniVer": "√",
   * "unixVer": "3.9.0"
   * }
   * }
   * }
   * @example ```typescript
   * import { request } from '@/utils/request/uni';
   * var result = request.get<T>({
   *   url: 'http://192.168.12.106:8080/postHalo', //仅为示例，并非真实接口地址。
   *   params: { value }
   * },{
   *   errorMessageMode
   * });
   * request.abort();
   * ```
   */
  abort() {
    const task = this.getTask();
    task?.abort();
  }

  /** 文件上传 */
  async uploadFile<T = AjaxResult>(
    config: Omit<UniRequestOptions, 'method'>,
    options?: RequestOptions,
  ): Promise<T> {
    const fnType = 'upload';
    // let conf: CreateUniRequestOptions = cloneDeep(config as CreateUniRequestOptions);
    const {
      // authenticationScheme: _authenticationScheme,
      transform: _transform,
      requestOptions: _requestOptions,
      ...thisOptions
    } = this.options;
    let conf: CreateUniRequestOptions = Object.assign({}, thisOptions, cloneDeep(config));

    const transform = this.getTransform();

    const { requestOptions } = this.options;

    const opt: RequestOptions = Object.assign({}, requestOptions, options, { provider: 'uni' });

    const { beforeRequestHook, requestCatchHook, transformResponseHook } = transform || {};
    if (beforeRequestHook && isFunction(beforeRequestHook)) {
      conf = await beforeRequestHook(conf, opt, this, fnType);
    }
    conf.requestOptions = opt;

    const { onProgress } = conf || {};
    return new Promise((resolve, reject) => {
      /** 异常处理器 */
      const catchHandler = (error: any) => {
        if (requestCatchHook && isFunction(requestCatchHook)) {
          reject(requestCatchHook(error, conf, opt, this, fnType));
          return;
        }
        reject(error);
      };
      this.uniTask = uni.uploadFile({
        ...conf,
        success: async (res: UniRequestSuccessResult<AjaxResult>) => {
          const statusCode = res.statusCode;
          if (statusCode < 400) {
            if (transformResponseHook && isFunction(transformResponseHook)) {
              try {
                const ret = await transformResponseHook(res, opt);
                resolve(ret);
              } catch (err) {
                reject(catchHandler(err));
              }
              return;
            }
            resolve(res as unknown as Promise<T>);
          } else {
            const statusMsg = getStatusLable(statusCode);
            const ex: UniRequestFailResult<AjaxResult> = {
              errCode: statusCode,
              errSubject: statusMsg,
              data: res.data,
              errMsg: `${statusCode} - ${statusMsg}`,
            };
            reject(catchHandler(ex));
          }
        },
        fail: (ex: UniRequestFailResult<AjaxResult>) => {
          reject(catchHandler(ex));
        },
      });
      this.getTask<UniApp.UploadTask>().onProgressUpdate((res) => {
        if (onProgress && isFunction(onProgress)) {
          onProgress({
            progress: res.progress,
            loaded: res.totalBytesSent,
            total: res.totalBytesExpectedToSend,
          });
        }
        // 测试取消上传任务。
        // this.uniTask.abort();
      });
    });
  }

  /** 文件下载 */
  async downloadFile<T = { statusCode: number; tempFilePath: string }>(
    config: Omit<UniRequestOptions, 'method'>,
    options?: RequestOptions,
  ): Promise<T> {
    // let conf: CreateUniRequestOptions = cloneDeep(config as CreateUniRequestOptions);
    const fnType = 'download';
    const {
      // authenticationScheme: _authenticationScheme,
      transform: _transform,
      requestOptions: _requestOptions,
      ...thisOptions
    } = this.options;
    let conf: CreateUniRequestOptions = Object.assign({}, thisOptions, cloneDeep(config));

    const transform = this.getTransform();

    const { requestOptions } = this.options;

    const opt: RequestOptions = Object.assign({}, requestOptions, options, {
      provider: 'uni',
      isReturnNativeResponse: true,
    });

    const { beforeRequestHook, requestCatchHook, transformResponseHook } = transform || {};
    if (beforeRequestHook && isFunction(beforeRequestHook)) {
      conf = await beforeRequestHook(conf, opt, this, fnType);
    }
    conf.requestOptions = opt;

    const { onProgress } = conf || {};
    return new Promise((resolve, reject) => {
      /** 异常处理器 */
      const catchHandler = (error: any) => {
        if (requestCatchHook && isFunction(requestCatchHook)) {
          reject(requestCatchHook(error, conf, opt, this, fnType));
          return;
        }
        reject(error);
      };
      this.uniTask = uni.downloadFile({
        ...conf,
        header: {
          ...config.header,
          ignoreCancelToken: true,
        },
        // method: 'GET', // 为保证直接使用 uni.downloadFile 的同步性，放入拦截器中处理。
        success: async (res: UniRequestSuccessResult<AjaxResult>) => {
          const statusCode = res.statusCode;
          if (statusCode < 400) {
            if (transformResponseHook && isFunction(transformResponseHook)) {
              try {
                const ret = await transformResponseHook(res, opt);
                resolve(ret);
              } catch (err) {
                reject(catchHandler(err));
              }
              return;
            }
            resolve(res as unknown as Promise<T>);
          } else {
            const statusMsg = getStatusLable(statusCode);
            const ex: UniRequestFailResult<AjaxResult> = {
              errCode: statusCode,
              errSubject: statusMsg,
              data: res.data,
              errMsg: `${statusCode} - ${statusMsg}`,
            };
            reject(catchHandler(ex));
          }
        },
        fail: (ex: UniRequestFailResult<AjaxResult>) => {
          reject(catchHandler(ex));
        },
      });
      this.getTask<UniApp.DownloadTask>().onProgressUpdate((res) => {
        if (onProgress && isFunction(onProgress)) {
          onProgress({
            progress: res.progress,
            loaded: res.totalBytesWritten,
            total: res.totalBytesExpectedToWrite,
          });
        }
        // 测试取消下载任务。
        // this.uniTask.abort();
      });
    });
  }

  /** 支持表单数据 */
  supportFormData(config: CreateUniRequestOptions) {
    const headers = config.header || this.options.header;
    const contentType = headers?.['Content-Type'] || headers?.['content-type'];

    if (
      contentType !== ContentTypeEnum.FORM_URLENCODED ||
      !Reflect.has(config, 'data') ||
      config.method?.toUpperCase() === RequestEnum.GET
    ) {
      return config;
    }

    return {
      ...config,
      data: stringify(config.data, { arrayFormat: 'brackets' }),
    };
  }

  /** get 请求 */
  get<T = AjaxResult>(config: UniRequestOptions, options?: RequestOptions): Promise<T> {
    return this.request<T>({ ...config, method: 'GET' }, options);
  }

  /** post 请求 */
  post<T = AjaxResult>(config: UniRequestOptions, options?: RequestOptions): Promise<T> {
    return this.request<T>({ ...config, method: 'POST' }, options);
  }

  /** put 请求 */
  put<T = AjaxResult>(config: UniRequestOptions, options?: RequestOptions): Promise<T> {
    return this.request<T>({ ...config, method: 'PUT' }, options);
  }

  /** delete 请求 */
  delete<T = AjaxResult>(config: UniRequestOptions, options?: RequestOptions): Promise<T> {
    return this.request<T>({ ...config, method: 'DELETE' }, options);
  }

  /** request 请求 */
  async request<T = AjaxResult>(config: UniRequestOptions, options?: RequestOptions): Promise<T> {
    const fnType = 'request';
    // let conf: CreateUniRequestOptions = cloneDeep(config as CreateUniRequestOptions);
    const {
      // authenticationScheme: _authenticationScheme,
      transform: _transform,
      requestOptions: _requestOptions,
      ...thisOptions
    } = this.options;
    let conf: CreateUniRequestOptions = Object.assign({}, thisOptions, cloneDeep(config));

    const transform = this.getTransform();

    const { requestOptions } = this.options;

    const opt: RequestOptions = Object.assign({}, requestOptions, options, { provider: 'uni' });

    const { beforeRequestHook, requestCatchHook, transformResponseHook } = transform || {};
    if (beforeRequestHook && isFunction(beforeRequestHook)) {
      conf = await beforeRequestHook(conf, opt, this, fnType);
    }
    conf.requestOptions = opt;

    conf = this.supportFormData(conf);

    return new Promise((resolve, reject) => {
      /** 异常处理器 */
      const catchHandler = (error: any) => {
        if (requestCatchHook && isFunction(requestCatchHook)) {
          reject(requestCatchHook(error, conf, opt, this, fnType));
          return;
        }
        reject(error);
      };
      this.uniTask = uni.request({
        ...conf,
        success: async (res: UniRequestSuccessResult<AjaxResult>) => {
          const statusCode = res.statusCode;
          if (statusCode < 400) {
            if (transformResponseHook && isFunction(transformResponseHook)) {
              try {
                const ret = await transformResponseHook(res, opt);
                resolve(ret);
              } catch (err) {
                reject(catchHandler(err));
              }
              return;
            }
            resolve(res as unknown as Promise<T>);
          } else {
            const statusMsg = getStatusLable(statusCode);
            const ex: UniRequestFailResult<AjaxResult> = {
              errCode: statusCode,
              errSubject: statusMsg,
              data: res.data,
              errMsg: `${statusCode} - ${statusMsg}`,
            };
            reject(catchHandler(ex));
          }
        },
        fail: (ex: UniRequestFailResult<AjaxResult>) => {
          reject(catchHandler(ex));
        },
      });
      // 测试取消请求任务。
      // this.uniTask.abort();
    });
  }
}
